package biolabs.mcl

import biolabs.core.Application
import biolabs.mcl.syntax._

import java.lang.reflect.Type
import java.lang.reflect.TypeVariable

import scala.collection.mutable.{Map => MMap}

object TypeVar {

	val IMPLICIT_VAR = "_"

	def apply(tv: TypeVariable[_]): TypeVar =
		apply(tv, Nil)

	def apply(tv: TypeVariable[_], typeVarsInCreation: List[TypeVar]): TypeVar =
		typeVarsInCreation collectFirst { case v if v.name == tv.getName() => v } match {
			case Some(v) => v
			case None => new JavaTypeVar(tv, typeVarsInCreation)
		}

	def analyze(ast: TypeParamAST, model: ModelDefinition, resolvedTypeVars: MMap[String, TypeVar], app: Application): AnalysisResult[TypeVar] = {
		// check if it is an implicit type parameter referring to self
		if(ast.name == IMPLICIT_VAR) {
			ast match {
				case UpperBoundedTypeParamAST(name, bound) =>
					// cannot have explicit upper bounds
					return AnalysisError("An implicit type parameter \"_\" cannot have explicit upper bounds")

				case UnboundedTypeParamAST(name) =>
					return AnalysisSuccess(new TypeVarSelf(model))
			}
		}

		try {
			AnalysisSuccess(new ModelTypeVar(ast, model, resolvedTypeVars, app))
		} catch {
			case e: SemanticException => AnalysisError(e.getMessage())
		}
	}

}

/**
 * Represents a type variable of a model.
 */
abstract class TypeVar extends SingleType {
	val name: String
	val upperBounds: List[SingleType]

	override def isAssignableFrom(sub: SingleType): Boolean =
		sub match {
			case tv: TypeVar =>
				this == tv || tv.upperBounds.exists { isAssignableFrom(_) }
			case _ =>
				false
		}

	override def isDescendantOf(ancestor: Class[_]): Boolean =
		upperBounds exists { _.isDescendantOf(ancestor) }

	override def getTypeArgumentsOf(ancestor: Class[_]) =
		upperBounds collectFirst({ case t if t.isDescendantOf(ancestor) => t.getTypeArgumentsOf(ancestor) }) match {
			case Some(args) => args
			case None => throw new IllegalArgumentException(this + " is not a descendant of " + ancestor)
		}

	override def getDelegateCandidates(interaction: ActualType) =
		upperBounds flatMap { _.getDelegateCandidates(interaction) }

	override def toString() =
		"type variable " + name

	override def hashCode() =
		name.hashCode()

	override def equals(other: Any): Boolean = other match {
		case that: TypeVar => isEqualTo(that, Nil)
		case _ => false
	}

	override def isEqualTo(other: SingleType, comparing: List[(TypeVar, TypeVar)]) = other match {
		case that: TypeVar => isEqualTo(that, comparing)
		case _ => false
	}

	private def isEqualTo(that: TypeVar, comparing: List[(TypeVar, TypeVar)]): Boolean = {
		comparing collectFirst { case (a, b) if (a eq this) && (b eq that) => } match {
			case Some(_) =>
				true
			case None =>
				// TODO: should be commutative with respect to order of upper bounds
				name == that.name && upperBounds.corresponds(that.upperBounds)((a,b) => a.isEqualTo(b, (this, that)::comparing))
		}
	}
}

class ModelTypeVar(ast: TypeParamAST, model: ModelDefinition, resolvedTypeVars: MMap[String, TypeVar], app: Application) extends TypeVar {

	val name = ast.name

	resolvedTypeVars(name) = this

	override val upperBounds = ast match {
		case UnboundedTypeParamAST(name) =>
			Nil
		case UpperBoundedTypeParamAST(name, bound) =>
			MultiType.analyze(bound, model, resolvedTypeVars, app) match {
				case AnalysisSuccess(upperBound) =>
					upperBound.types
				case AnalysisError(msg) =>
					throw new SemanticException(msg)
			}
	}
}

class JavaTypeVar(tv: TypeVariable[_], typeVarsInCreation: List[TypeVar]) extends TypeVar {
	override val name = tv.getName()
	override val upperBounds = safeCast(tv).getBounds() map { SingleType(_, this::typeVarsInCreation) } toList;

	private def safeCast(tv: TypeVariable[_]): TypeVariable[Class[_]] =
		tv.getGenericDeclaration() match {
			case _: Class[_] =>
				tv.asInstanceOf[TypeVariable[Class[_]]]
			case _ =>
				throw new IllegalArgumentException(tv + " is not declared on a type. Cannot convert to TypeVar.")
		}
}

class TypeVarSelf(model: ModelDefinition) extends TypeVar {
	override val name = "Self"
	lazy val upperBound = model.asType()
	override lazy val upperBounds = List(upperBound)
}